Current File : /home/jeconsul/public_html/wp-content/plugins/suremails/src/screens/dashboard/chart.js
// Chart.jsx
import { useState, useEffect, useRef } from '@wordpress/element';
import {
	Container,
	Input,
	Label,
	DatePicker,
	LineChart,
	Button,
} from '@bsf/force-ui';
import apiFetch from '@wordpress/api-fetch';
import Title from '@components/title/title';
import { __ } from '@wordpress/i18n';
import {
	cn,
	format,
	getDatePlaceholder,
	getSelectedDate,
	getLastNDays,
} from '@utils/utils';
import { ChartColumn, Calendar, X, Plus } from 'lucide-react';
import { useQueryClient } from '@tanstack/react-query';
import { useNavigate } from 'react-router-dom';

export const Chart = ( {
	totalSent = 0,
	totalFailed = 0,
	chartData = [],
	hasConnections = true,
} ) => {
	const [ selectedDates, setSelectedDates ] = useState( {
		from: null,
		to: null,
	} );
	const [ isDatePickerOpen, setIsDatePickerOpen ] = useState( false );
	const [ dataToShow, setDataToShow ] = useState( [] );
	const [ sent, setSent ] = useState( 0 );
	const [ failed, setFailed ] = useState( 0 );
	const containerRef = useRef( null );
	const queryClient = useQueryClient();
	const navigate = useNavigate();

	// Effect to process initial chartData
	useEffect( () => {
		if ( chartData.length === 0 ) {
			setSent( 0 );
			setFailed( 0 );
			setDataToShow( [] );
		} else {
			const sortedChartData = [ ...chartData ].sort(
				( a, b ) => new Date( a.created_at ) - new Date( b.created_at )
			);

			const formattedInitialChartData = sortedChartData.map(
				( data ) => ( {
					month: format(
						new Date( data.created_at ),
						'MMM dd, yyyy'
					),
					sent: parseInt( data.total_sent, 10 ) || 0,
					failed: parseInt( data.total_failed, 10 ) || 0,
				} )
			);
			setDataToShow( formattedInitialChartData );
			setSent( totalSent || 0 );
			setFailed( totalFailed || 0 );
		}
	}, [ totalSent, totalFailed, chartData ] );

	// Function to fetch chart data based on selected dates
	const fetchChartData = async ( dates ) => {
		if ( ! dates.from ) {
			return;
		}

		const formattedStartDate = format(
			new Date( dates.from ),
			'yyyy/MM/dd'
		);
		const formattedEndDate = dates.to
			? format( new Date( dates.to ), 'yyyy/MM/dd' )
			: formattedStartDate;

		try {
			const response = await apiFetch( {
				path: '/suremails/v1/email-stats',
				method: 'POST',
				headers: {
					'X-WP-Nonce': window.suremails?.nonce,
					'Content-Type': 'application/json',
				},
				body: JSON.stringify( {
					start_date: formattedStartDate,
					end_date: formattedEndDate,
				} ),
			} );

			if ( response.success ) {
				const sortedNewChartData = [ ...response.data.chart_data ].sort(
					( a, b ) =>
						new Date( a.created_at ) - new Date( b.created_at )
				);

				const newChartData = sortedNewChartData.map( ( data ) => ( {
					month: format(
						new Date( data.created_at ),
						'MMM dd, yyyy'
					),
					sent: parseInt( data.total_sent, 10 ) || 0,
					failed: parseInt( data.total_failed, 10 ) || 0,
				} ) );

				setSent( response.data.total_sent || 0 );
				setFailed( response.data.total_failed || 0 );
				setDataToShow( newChartData );
			} else {
				setSent( 0 );
				setFailed( 0 );
				setDataToShow( [] );
			}
		} catch ( error ) {
			setSent( 0 );
			setFailed( 0 );
			setDataToShow( [] );
		}
	};

	// Effect to fetch chart data when selectedDates change
	useEffect( () => {
		if ( selectedDates.from ) {
			fetchChartData( selectedDates );
		}
	}, [ selectedDates.from, selectedDates.to ] );

	// Handler to clear filters and reset to default data from Query Client
	const handleClearFilters = () => {
		setSelectedDates( { from: null, to: null } );

		// Retrieve cached 'dashboard-data' from Query Client
		const dashboardData = queryClient.getQueryData( [ 'dashboard-data' ] );

		if ( dashboardData ) {
			const sortedChartData = [ ...dashboardData.chart_data ].sort(
				( a, b ) => new Date( a.created_at ) - new Date( b.created_at )
			);

			const formattedDefaultChartData = sortedChartData.map(
				( data ) => ( {
					month: format(
						new Date( data.created_at ),
						'MMM dd, yyyy'
					),
					sent: parseInt( data.total_sent, 10 ) || 0,
					failed: parseInt( data.total_failed, 10 ) || 0,
				} )
			);

			setDataToShow( formattedDefaultChartData );
			setSent( dashboardData.total_sent || 0 );
			setFailed( dashboardData.total_failed || 0 );
		} else {
			// If 'dashboard-data' is not available, reset to empty or default state
			setSent( 0 );
			setFailed( 0 );
			setDataToShow( [] );
		}
	};

	// Handlers for DatePicker
	const handleDateApply = ( dates ) => {
		const { from, to } = dates;

		if ( from && to ) {
			const fromDate = new Date( from );
			const toDate = new Date( to );

			if ( fromDate > toDate ) {
				// Swap the dates to ensure 'from' is earlier than 'to'
				setSelectedDates( { from: to, to: from } );
			} else {
				setSelectedDates( dates );
			}
		} else if ( from && ! to ) {
			setSelectedDates( { from, to: from } );
		} else {
			setSelectedDates( { from: null, to: null } );
		}
		setIsDatePickerOpen( false );
	};

	const handleDateCancel = () => {
		setIsDatePickerOpen( false );
	};

	// Click Outside Handler using useEffect
	useEffect( () => {
		function handleClickOutside( event ) {
			if (
				isDatePickerOpen &&
				containerRef.current &&
				! containerRef.current.contains( event.target )
			) {
				setIsDatePickerOpen( false );
			}
		}

		// Bind the event listener
		document.addEventListener( 'mousedown', handleClickOutside );
		return () => {
			// Unbind the event listener on cleanup
			document.removeEventListener( 'mousedown', handleClickOutside );
		};
	}, [ isDatePickerOpen ] );

	// Formatter for X-Axis
	const formatXAxis = ( tickItem ) => {
		return format( new Date( tickItem ), 'MMM dd, yyyy' );
	};

	return (
		<Container
			containerType="flex"
			direction="column"
			gap="xs"
			className="w-full h-full p-4 rounded-xl bg-background-primary border-border-subtle border-0.5"
		>
			<Container.Item className="flex items-center justify-between w-full p-1">
				<Title title={ __( 'Overview', 'suremails' ) } tag="h3" />

				<div className="flex items-center gap-2">
					{ selectedDates.from || selectedDates.to ? (
						<Button
							variant="link"
							size="xs"
							icon={ <X /> }
							onClick={ handleClearFilters }
							className="text-button-danger no-underline focus:ring-0 [box-shadow:none] focus:[box-shadow:none] hover:no-underline hover:text-button-danger"
							aria-label={ __( 'Clear Filters', 'suremails' ) }
						>
							{ __( 'Clear Filters', 'suremails' ) }
						</Button>
					) : null }

					<div className="relative" ref={ containerRef }>
						<Input
							type="text"
							size="sm"
							value={ getSelectedDate( selectedDates ) }
							suffix={
								<Calendar className="text-icon-secondary" />
							}
							onClick={ () =>
								setIsDatePickerOpen( ( prev ) => ! prev )
							}
							placeholder={ getDatePlaceholder() }
							className="w-auto min-w-[200px] cursor-pointer [&>input]:min-h-8 rounded-sm shadow-sm border border-border-subtle"
							readOnly
							aria-label={ __(
								'Select Date Range',
								'suremails'
							) }
						/>

						{ isDatePickerOpen && (
							<div className="absolute z-10 mt-2 rounded-lg shadow-lg right-0 bg-background-primary">
								<DatePicker
									applyButtonText={ __(
										'Apply',
										'suremails'
									) }
									cancelButtonText={ __(
										'Cancel',
										'suremails'
									) }
									selectionType="range"
									showOutsideDays={ false }
									variant="presets"
									onApply={ handleDateApply }
									onCancel={ handleDateCancel }
									selected={ getLastNDays( 30 ) }
								/>
							</div>
						) }
					</div>
				</div>
			</Container.Item>

			<Container.Item
				className={ cn(
					'w-full flex items-stretch justify-between gap-1 bg-background-secondary rounded-lg',
					dataToShow.length > 0 ? 'p-1' : 'p-0'
				) }
			>
				{ /* Chart Container */ }
				<Container
					className={ cn(
						'w-full flex flex-col flex-1 p-3 overflow-hidden bg-background-primary',
						dataToShow.length > 0 ? 'rounded-md shadow-sm' : ''
					) }
					containerType="flex"
					direction="column"
				>
					{ dataToShow.length > 0 ? (
						<div className="flex-1 w-full">
							<div className="w-full h-full min-h-[248px] min-[1427px]:min-h-[228px]">
								<LineChart
									data={ dataToShow }
									dataKeys={ [ 'sent', 'failed' ] }
									colors={ [
										{ stroke: '#0EA5E9' }, // Color for "Email Sent"
										{ stroke: '#A855F7' }, // Color for "Email Failed"
									] }
									showXAxis={ false }
									showYAxis={ false }
									showTooltip
									showCartesianGrid={ true }
									tooltipIndicator="dot"
									tickFormatter={ formatXAxis }
									xAxisDataKey="month"
									chartWidth="100%"
									chartHeight="100%"
									tooltipLabelKey="month"
									lineChartWrapperProps={ {
										margin: {
											top: 30,
											bottom: 30,
											right: 5,
											left: 5,
										},
									} }
								/>
							</div>
						</div>
					) : (
						<div className="flex flex-col items-center justify-center h-full  min-[1427px]:min-h-[236px] min-h-[256px] gap-3">
							<div className="flex flex-col items-center justify-center w-[29.375rem]">
								<ChartColumn className="mb-3" />
								<div className="flex flex-col items-center space-y-1">
									<Label
										tag="p"
										className="text-sm font-medium text-center text-text-primary"
									>
										{ __(
											'No Email Stats Available',
											'suremails'
										) }
									</Label>
									<Label
										tag="p"
										className="text-sm font-normal text-center text-text-secondary"
									>
										{ __(
											'Once your emails start sending, you’ll see detailed stats here to help you monitor and manage your email activity.',
											'suremails'
										) }
									</Label>
								</div>
							</div>
							<div>
								{ ! hasConnections && (
									<Button
										variant="primary"
										size="sm"
										icon={ <Plus /> }
										iconPosition="left"
										onClick={ () =>
											navigate( '/connections', {
												state: {
													openDrawer: true,
												},
											} )
										}
										className="font-medium"
									>
										{ __( 'Add Connection', 'suremails' ) }
									</Button>
								) }
							</div>
						</div>
					) }
				</Container>

				{ /* Statistics Cards */ }
				{ dataToShow.length > 0 && (
					<Container
						containerType="flex"
						direction="column"
						className="w-[30%] gap-1 bg-background-secondary rounded-lg"
					>
						<Container.Item className="flex flex-col items-start justify-center flex-1 p-3 text-left rounded-md shadow-sm bg-background-primary">
							<div className="flex items-center mb-1">
								<div className="w-3 h-3 rounded bg-[#0EA5E9]"></div>
								<Label className="p-1 text-xs text-text-tertiary">
									{ __( 'Email Sent', 'suremails' ) }
								</Label>
							</div>
							<Label className="p-1 mt-3 text-4xl font-semibold text-text-primary leading-[44px]">
								{ String( sent ) }
							</Label>
						</Container.Item>

						<Container.Item className="flex flex-col items-start justify-center flex-1 p-3 text-left rounded-md shadow-sm bg-background-primary">
							<div className="flex items-center mb-1">
								<div className="w-3 h-3 rounded bg-[#A855F7]"></div>
								<Label className="p-1 text-xs text-text-tertiary">
									{ __( 'Email Failed', 'suremails' ) }
								</Label>
							</div>
							<Label className="p-1 mt-3 text-4xl font-semibold text-text-primary leading-[44px]">
								{ String( failed ) }
							</Label>
						</Container.Item>
					</Container>
				) }
			</Container.Item>
		</Container>
	);
};

export default Chart;